package fr.xgouchet.wedding.client.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;

import com.google.gwt.core.client.JsArray;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle;

import fr.xgouchet.wedding.client.jsni.JSTask;
import fr.xgouchet.wedding.shared.model.Task;

/**
 * JSON service dedicated to Tasks info
 * 
 * @author x.gouchet
 * 
 */
public class TasksService {

	/**
	 * Fetch the tasks list
	 * 
	 * @param category
	 *            the tasks category filter
	 * @param status
	 *            the task status filter
	 * @param asyncCallback
	 *            the callback
	 */
	public static void getTasksList(final String category, final String status,
			final AsyncCallback<ArrayList<Task>> asyncCallback) {
		if ((TASKS != null) && (TASKS.size() != 0)) {
			asyncCallback.onSuccess(getTasksDetailsList(category, status));
			return;
		}

		String url = URL.encode(JSON_URL_TASKS_LIST);

		// Send request to server and catch any errors.
		RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url);
		builder.setHeader("Content-type", "application/x-www-form-urlencoded");
		// builder.setHeader("X-XSRF-Cookie", Cookies.getCookie("myCookieKey"));

		try {
			builder.sendRequest(null, new RequestCallback() {
				public void onError(Request request, Throwable exception) {
					asyncCallback.onFailure(new Throwable(
							"Couldn't retrieve JSON (Request Error)"));
				}

				public void onResponseReceived(Request request,
						Response response) {
					if (200 == response.getStatusCode()) {
						toCachedData(asArrayOfTasks(response.getText()));
						asyncCallback.onSuccess(getTasksDetailsList(category,
								status));
					} else {
						asyncCallback.onFailure(new Throwable(
								"Couldn't retrieve JSON (Reponse status : "
										+ response.getStatusText() + ")"));
					}
				}
			});
		} catch (RequestException e) {
			asyncCallback.onFailure(new Throwable("Couldn't send request"));
		}
	}

	/**
	 * 
	 * @param category
	 *            the tasks category filter
	 * @param status
	 *            the task status filter
	 * @return the list of tasks
	 */
	public static ArrayList<Task> getTasksDetailsList(String category,
			String status) {
		CATEGORY_FILTER = category;
		STATUS_FILTER = status;
		ArrayList<Task> filteredTasks = getFilteredTasks();

		Collections.sort(filteredTasks, new Comparator<Task>() {
			public int compare(Task task1, Task task2) {
				if (task1.getDaysBeforeWedding() > task2.getDaysBeforeWedding())
					return -1;
				if (task1.getDaysBeforeWedding() < task2.getDaysBeforeWedding())
					return 1;
				return 0;
			}
		});

		return filteredTasks;
	}

	/**
	 * @param task
	 *            the task to save
	 * @param asyncCallback
	 *            the callback to which the data will be transmitted
	 */
	public static void saveTask(Task task,
			final AsyncCallback<Object> asyncCallback) {
		String url = URL.encode(JSON_URL_TASK_SAVE);

		// Send request to server and catch any errors.
		RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url);
		builder.setHeader("Content-type", "application/x-www-form-urlencoded");
		// builder.setHeader("X-XSRF-Cookie", Cookies.getCookie("myCookieKey"));

		// add post data
		String postData = (task.getID().length() > 0) ? ("id=" + task.getID() + "&")
				: "";
		postData += "title=" + task.getTitle() + "&";
		postData += "description=" + task.getDescription() + "&";
		postData += "category=" + task.getCategory() + "&";
		postData += "days=" + task.getDaysBeforeWedding() + "&";
		postData += "status=" + (task.isComplete() ? "1" : "0");
		postData = URL.encode(postData);

		try {
			builder.sendRequest(postData, new RequestCallback() {
				public void onError(Request request, Throwable exception) {
					asyncCallback.onFailure(new Throwable(
							"Couldn't retrieve JSON (Request Error)"));
				}

				public void onResponseReceived(Request request,
						Response response) {
					if (200 == response.getStatusCode()) {
						toCachedData(asArrayOfTasks(response.getText()));
						asyncCallback.onSuccess(getTasksDetailsList(
								CATEGORY_FILTER, STATUS_FILTER));
					} else {
						asyncCallback.onFailure(new Throwable(
								"Couldn't retrieve JSON (Reponse status : "
										+ response.getStatusText() + ")"));
					}
				}
			});
		} catch (RequestException e) {
			asyncCallback.onFailure(new Throwable("Couldn't send request"));
		}
	}

	/**
	 * @param ids
	 *            list of ids to delete
	 * @param asyncCallback
	 *            the callback to which the data will be transmitted
	 */
	public static void deleteTasks(ArrayList<String> ids,
			final AsyncCallback<Object> asyncCallback) {
		// delete guest from server
		String url = URL.encode(JSON_URL_TASK_DELETE);

		// Send request to server and catch any errors.
		RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url);
		builder.setHeader("Content-type", "application/x-www-form-urlencoded");
		// builder.setHeader("X-XSRF-Cookie", Cookies.getCookie("myCookieKey"));

		// Add post data
		String postData = "id=";
		Iterator<String> it = ids.iterator();
		while (it.hasNext()) {
			String id = it.next();
			postData += id + "+";
			// Remove guest from cache
			TASKS.remove(id);
		}
		postData = URL.encode(postData);

		try {
			builder.sendRequest(postData, new RequestCallback() {
				public void onError(Request request, Throwable exception) {
					asyncCallback.onFailure(new Throwable(
							"Couldn't retrieve JSON (Request Error)"));
				}

				public void onResponseReceived(Request request,
						Response response) {
					if (200 == response.getStatusCode()) {
						asyncCallback.onSuccess(null);
					} else {
						asyncCallback.onFailure(new Throwable(
								"Couldn't retrieve JSON (Reponse status : "
										+ response.getStatusText() + ")"));
					}
				}
			});
		} catch (RequestException e) {
			asyncCallback.onFailure(new Throwable("Couldn't send request"));
		}
	}

	/**
	 * @return the suggestion for tasks filters
	 */
	public static MultiWordSuggestOracle getTasksCategories() {
		MultiWordSuggestOracle cats = new MultiWordSuggestOracle();
		for (String filter : TASKS_FILTERS)
			cats.add(filter);
		return cats;
	}

	/**
	 * @return the list of tasks filters
	 */
	public static ArrayList<String> getFilters() {
		ArrayList<String> filters = new ArrayList<String>();
		for (String filter : TASKS_FILTERS)
			filters.add(filter);
		return filters;
	}

	/**
	 * Convert the string of JSON into JavaScript object.
	 * 
	 * @param json
	 *            json
	 * @return return
	 */
	private final static native JsArray<JSTask> asArrayOfTasks(String json) /*-{
		return eval(json);
	}-*/;

	/**
	 * @param data
	 *            the JSON data
	 */
	private final static void toCachedData(JsArray<JSTask> data) {
		if (TASKS == null)
			TASKS = new HashMap<String, Task>();
		for (int i = 0; i < data.length(); i++) {
			addTaskToCache(data.get(i));
		}
	}

	/**
	 * @param from
	 *            the JavaScript object
	 */
	private static void addTaskToCache(JSTask from) {
		Task to = new Task();
		to.setID(from.getID());
		to.setStatus(from.getStatus());
		to.setTitle(from.getTitle());
		to.setDescription(from.getDescription());
		to.setDaysBeforeWedding(from.getDays());
		to.setCategory(from.getCategory());
		TASKS.put(from.getID(), to);
	}

	/**
	 * Filters the cached data
	 * 
	 * @return a list of valid tasks
	 */
	private static ArrayList<Task> getFilteredTasks() {
		ArrayList<Task> list = new ArrayList<Task>();
		Collection<Task> collec = TASKS.values();
		Iterator<Task> it = collec.iterator();

		while (it.hasNext()) {
			Task task = it.next();
			if (validTaskCategory(task.getCategory()))
				if (validTaskStatus(task.isComplete(), task.getDeadline()))
					list.add(task);
		}

		return list;
	}

	/**
	 * @param complete
	 *            is the task complete
	 * @param deadline
	 *            the task's deadline
	 * @return if the task is valid or not
	 */
	private static boolean validTaskStatus(boolean complete, Date deadline) {
		if ((STATUS_FILTER == null) || (STATUS_FILTER.length() == 0))
			return true;
		if (STATUS_FILTER.equals("open"))
			return (!complete);
		if (STATUS_FILTER.equals("closed"))
			return complete;
		if (STATUS_FILTER.equals("overdue"))
			return ((deadline.before(new Date())) && (!complete));
		return true;
	}

	/**
	 * @param category
	 *            the task's category
	 * @return if the task is valid or not
	 */
	private static boolean validTaskCategory(String category) {
		if ((CATEGORY_FILTER == null) || (CATEGORY_FILTER.length() == 0))
			return true;
		return category.equals(CATEGORY_FILTER);
	}

	/** Local cached data */
	private static HashMap<String, Task> TASKS = null;

	/** Cached Tasks filters */
	private static final String[] TASKS_FILTERS = new String[] { "BEAUTE",
			"BUDGET", "CEREMONIE", "DECORATION", "DEMARCHES", "DIVERS",
			"HABILLEMENT", "INVITES", "NOCES", "PRESTATAIRES", "RECEPTION" };

	/** Cached filters */
	private static String CATEGORY_FILTER = "";

	/** Cached filters */
	private static String STATUS_FILTER = "";

	/** Root URL */
	private static final String JSON_URL_ROOT = "http://localhost/GWT/gwt_on-dit-oui/";

	/** URL of the service listing the tasks */
	private static final String JSON_URL_TASKS_LIST = JSON_URL_ROOT
			+ "list_tasks.php";

	/** URL of the service listing the tasks */
	private static final String JSON_URL_TASK_SAVE = JSON_URL_ROOT
			+ "save_task.php";

	/** URL of the service deleting tasks */
	private static final String JSON_URL_TASK_DELETE = JSON_URL_ROOT
			+ "delete_tasks.php";
}
